package cn.wangfeixiong.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.wangfeixiong.util.Safe;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

@Slf4j
public class RequestLogAndTraceFilter extends OncePerRequestFilter {

   private static final String NOT_PRINT = "配置不打印";

    private static final String NOT_PRINT_FILE = "文件流不打印";

    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
                                    @NotNull FilterChain filterChain) throws IOException, ServletException {
        RequestLogRuleProperties.Rule rule = matchRule(request.getRequestURI());
        CachingHttpServletResponse cacheResponse = null;
        boolean print = false;
        String traceId = IdUtil.fastUUID();
        StopWatch stopWatch = new StopWatch();
        try {
            stopWatch.start();
            MDC.put("traceId", traceId);
            request.setAttribute("RequestStartTime", System.currentTimeMillis());
            boolean isPreflight = RequestMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod());
            if (isPreflight) {
                filterChain.doFilter(request, response);
                return;
            }

            if (rule.getPrint()) {
                print = true;
                request = new CachingHttpServletRequest(request);
                cacheResponse = new CachingHttpServletResponse(response);
                filterChain.doFilter(request, cacheResponse);
            } else {
                filterChain.doFilter(request, response);
            }

        } finally {
            stopWatch.stop();
            if(print){
                HttpServletRequest finalRequest = request;
                CachingHttpServletResponse finalCacheResponse = cacheResponse;
                Safe.safeRun(() -> {
                    MDC.put("traceId", traceId);
                    try {
                        log.info("------(接口|请求){}耗时:{}ms------\n请求url: {}\n请求header: {}\n请求body: {}\n响应header: {}\n响应data: {}",
                                 finalRequest.getRequestURI(),
                                 stopWatch.getTotalTimeMillis(),
                                 finalRequest.getRequestURL(),
                                 headers(finalRequest, rule),
                                 request((CachingHttpServletRequest)finalRequest, rule),
                                 headers(response, rule),
                                 response(finalCacheResponse, response, rule));
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    MDC.clear();
                });

            }
        }

    }

    public @NotNull RequestLogRuleProperties.Rule matchRule(String requestURI) {
        RequestLogRuleProperties properties = SpringUtil.getBean(RequestLogRuleProperties.class);
        List<RequestLogRuleProperties.Rule> rules = properties.getRules();

        if (CollUtil.isEmpty(rules)) {
            return properties.defaultRule();
        }
        for (RequestLogRuleProperties.Rule rule : rules) {
            if (requestURI.startsWith(rule.getUrlPrefix())) {
                // 全局关闭的优先级最高
                if(!properties.getPrint()){
                    rule.setPrint(false);
                }
                return rule;
            }
        }
        return properties.defaultRule();
    }


    public Set<String> headers(HttpServletRequest request,
                               RequestLogRuleProperties.Rule rule) {
        Set<String> headers = new HashSet<>();
        if(!rule.getPrintRequestHeader()){
            headers.add(NOT_PRINT);
            return headers;
        }

        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            headers.add(headerName + ": " + headerValue);
        }
        return headers;
    }

    public Set<String> headers(HttpServletResponse response,
                               RequestLogRuleProperties.Rule rule) {
        Set<String> headers = new HashSet<>();
        if(!rule.getPrintResponseHeader()){
            headers.add(NOT_PRINT);
            return headers;
        }
        Collection<String> headerNames = response.getHeaderNames();
        if (CollUtil.isEmpty(headerNames)) {
            return headers;
        }
        headerNames.forEach(headerName -> {
            String headerValue = response.getHeader(headerName);
            headers.add(headerName + ": " + headerValue);
        });
        return headers;
    }

    public String response(CachingHttpServletResponse cachingResponse,
                           HttpServletResponse response,
                           RequestLogRuleProperties.Rule rule) throws IOException {

        if (Objects.isNull(cachingResponse)) {
            return null;
        }
        byte[] responseContent = cachingResponse.getContent();
        response.getOutputStream().write(responseContent);
        response.getOutputStream().flush();
        if (!rule.getPrintResponse()) {
            return NOT_PRINT;
        }
        if(isFileDownloadResponse(cachingResponse)){
            return NOT_PRINT_FILE;
        }
        return StringUtils.abbreviate(new String(responseContent), rule.getResponseLimitSize());
    }

    private boolean isFileDownloadResponse(CachingHttpServletResponse response) {
        // 检查 Content-Disposition 头（附件下载）
        String contentDisposition = response.getHeader("Content-Disposition");
        if (contentDisposition != null && contentDisposition.contains("attachment")) {
            return true;
        }

        // 检查 Content-Type 是否为二进制类型
        String contentType = response.getContentType();
        if (contentType != null) {
            return contentType.startsWith("application/octet-stream") ||
                contentType.startsWith("application/pdf") ||
                contentType.startsWith("application/zip") ||
                contentType.startsWith("image/") ||
                contentType.startsWith("video/") ||
                contentType.startsWith("audio/");
        }

        return false;
    }

    private boolean isMultipartRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        return contentType != null && (
            contentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE) ||
                contentType.startsWith(MediaType.MULTIPART_MIXED_VALUE) ||
                contentType.startsWith(MediaType.MULTIPART_RELATED_VALUE) ||
                contentType.startsWith("image/") ||
                contentType.startsWith("video/") ||
                contentType.startsWith("audio/")
        );

    }

    public String request(CachingHttpServletRequest request, RequestLogRuleProperties.Rule rule){
        if (Objects.isNull(request)) {
            return StrUtil.EMPTY;
        }
        if (!rule.getPrintRequestBody()) {
            return NOT_PRINT;
        }
        if (isMultipartRequest(request)) {
            return NOT_PRINT_FILE;
        }
        return StringUtils.abbreviate(request.getBody(), rule.getRequestBodyLimitSize());
    }
}
